﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Taxonomy;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Diagnostics;
using Microsoft.SharePoint.Utilities;

namespace XComplica.SharePoint
{
    public class MetaFilteredFieldControl : BaseFieldControl
    {
        TaxonomySession taxonomySession;
        TermStore termStore;

        protected Repeater rpSelected;
        protected Repeater rpTagPickers;

        public bool AllowMultipleValues { get; set; }

        public override object Value
        {
            get
            {
                EnsureChildControls();
                try
                {
                    using (SPMonitoredScope scope = new SPMonitoredScope("Get Value"))
                    {
                        SPFieldLookupValueCollection retVal = new SPFieldLookupValueCollection();


                        // loop through all the checkboxes and find the ones that have been checked.
                        foreach (RepeaterItem rpi in rpSelected.Items)
                        {
                            CheckBox cbItem = (CheckBox)rpi.FindControl("cbItem");
                            if (cbItem.Checked)
                            {
                                // foreach checked checkbox, create a new SPFieldLookupValue and add this to the returned collection
                                Literal litID = (Literal)rpi.FindControl("litID");
                                Literal litTitle = (Literal)rpi.FindControl("litTitle");
                                retVal.Add(new SPFieldLookupValue(int.Parse(litID.Text), litTitle.Text));
                            }
                        }



                        Debug.WriteLine(string.Format("Getting field value {0}", retVal));

                        // if no values are selected, return null
                        if (retVal.Count == 0)
                            return null;
                        // if multiple values are allowed, return the collection
                        else if (AllowMultipleValues)
                            return retVal;
                        // if only one value is expected, return the first.
                        else
                            return retVal[0];
                    }
                }
                catch (Exception ex)
                {
                    WriteToLog("Error getting field value of Metadata Filtered field", ex, TraceSeverity.Unexpected, EventSeverity.Error);
                    return null;
                }

            }
            set
            {
                EnsureChildControls();
                Debug.WriteLine(string.Format("Setting field value {0}", ItemFieldValue));

                try
                {
                    using (SPMonitoredScope scope = new SPMonitoredScope("Set Value"))
                    {
                        if (null != this.ItemFieldValue)
                        {
                            // the field type is custom, but based on a lookup
                            MetaFilteredField field = (MetaFilteredField)this.Field;
                            using (SPWeb web = Web.Site.OpenWeb(field.LookupWebId))
                            {
                                // open this list that this lookup gets its data from
                                SPList list = web.Lists[new Guid(field.LookupList)];

                                using (SPMonitoredScope scope1 = new SPMonitoredScope("Databing Pickers"))
                                {
                                    // Create a list of all the active (selected) items from the target list, and ckeck the checkboxes corresponding to these
                                    IList<SPListItem> activeItems;
                                    if (field.AllowMultipleValues)
                                    {
                                        activeItems = ActivateItems(list, (SPFieldLookupValueCollection)this.ItemFieldValue);
                                    }
                                    else
                                        activeItems = new List<SPListItem>() { ActivateItem(list, (SPFieldLookupValue)this.ItemFieldValue) };

                                    // populate the list of tag pickers
                                    IDictionary<Guid, IList<TaxonomyFieldValue>> activeTags = GetAllActiveMetaTags(activeItems, false);
                                    rpTagPickers.DataSource = activeTags;
                                    rpTagPickers.DataBind();
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    WriteToLog("Error setting field value of Metadata Filtered field", ex, TraceSeverity.Unexpected, EventSeverity.Error);
                }
            }
        }



        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            taxonomySession = new TaxonomySession(SPContext.Current.Site);
            //TODO: Currently ths field only works on the first term store, this should be upgraded at one point.
            termStore = taxonomySession.TermStores[0];
        }


        protected override void CreateChildControls()
        {

            if (this.Field == null || this.ControlMode == SPControlMode.Display)
                return;

            base.CreateChildControls();
            try
            {
                using (SPMonitoredScope scope = new SPMonitoredScope("Create Childcontrols"))
                {
                    rpSelected = TemplateContainer.FindControl("rpSelected") as Repeater;
                    rpSelected.ItemDataBound += new RepeaterItemEventHandler(rpSelected_ItemDataBound);

                    rpTagPickers = TemplateContainer.FindControl("rpTagPickers") as Repeater;
                    rpTagPickers.ItemDataBound += new RepeaterItemEventHandler(rpTagPickers_ItemDataBound);

                    MetaFilteredField field = (MetaFilteredField)this.Field;

                    // get the list items from the target lookup list and populate the list of choices
                    using (SPWeb web = Web.Site.OpenWeb(field.LookupWebId))
                    {
                        SPListItemCollection items = null;
                        using (SPMonitoredScope scope1 = new SPMonitoredScope("Databind Selections"))
                        {
                            SPList list = web.Lists[new Guid(field.LookupList)];
                            SPQuery q = new SPQuery();
                            items = list.GetItems(q);

                            rpSelected.DataSource = items;
                            rpSelected.DataBind();
                        }

                        using (SPMonitoredScope scope2 = new SPMonitoredScope("Databing Empty Pickers"))
                        {
                            // This code is only really necessary when the field has a null value. In this case, the Set event is not called and so we need to create
                            // the tag pickers. This can be done using the first list item since we just care about the srtructure of the field (content type) not the data
                            IDictionary<Guid, IList<TaxonomyFieldValue>> activeTags = GetAllActiveMetaTags(new List<SPListItem>() { items[0] }, true);
                            rpTagPickers.DataSource = activeTags;
                            rpTagPickers.DataBind();
                        }
                    }

                }
            }
            catch (Exception ex)
            {
                WriteToLog("Error creating child controls", ex, TraceSeverity.Unexpected, EventSeverity.Error);
            }
        }

        protected override string DefaultTemplateName
        {
            get
            {
                return "MetaFilteredFieldTemplate";
            }
        }

        /// <summary>
        /// Populate the tag pickers repeater
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void rpTagPickers_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            try
            {        
                if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
                {

                    TaxonomyWebTaggingControl tagPicker = (TaxonomyWebTaggingControl)e.Item.FindControl("tagPicker");
                    Literal litTitle = (Literal)e.Item.FindControl("litTitle");

                    KeyValuePair<Guid, IList<TaxonomyFieldValue>> data = (KeyValuePair<Guid, IList<TaxonomyFieldValue>>)e.Item.DataItem;

                    TermSet termSet = termStore.GetTermSet(data.Key);

                    litTitle.Text = termSet.Name;

                    InitTagPicker(tagPicker, termStore, termSet, data.Value);
                }
            }
            catch (Exception ex)
            {
                WriteToLog("Error populating a tag picker", ex, TraceSeverity.Unexpected, EventSeverity.Error);
            }
        }

        /// <summary>
        /// Populate the Repeater of selectable items
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void rpSelected_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            try
            {
                if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
                {

                    Literal litMetaTags = (Literal)e.Item.FindControl("litMetaTags");
                    Literal litID = (Literal)e.Item.FindControl("litID");
                    Literal litTitle = (Literal)e.Item.FindControl("litTitle");
                    CheckBox cbItem = (CheckBox)e.Item.FindControl("cbItem");

                    if (!this.AllowMultipleValues)
                        cbItem.CssClass = "singleValue";

                    SPListItem data = (SPListItem)e.Item.DataItem;
                    litMetaTags.Text = GetMetadata(data);
                    litID.Text = data.ID.ToString();
                    litTitle.Text = data.Title;
                }
            }
            catch (Exception ex)
            {
                WriteToLog("Error populating a selectable item", ex, TraceSeverity.Unexpected, EventSeverity.Error);
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            // The various bits of jQuery needed for this field are documented here. They have been minified and quote escaped before rendered to the output.
            StringBuilder jQuery = new StringBuilder();

            /*
             *  $.fn.refreshItems = function () {
             *      var scope = this;
             *      var allTags = new Array();
             *      $('#tagPickers input', scope).each(function () {
             *          var tags = $(this).val().split(';');
             *          $.merge(allTags, tags);
             *      });
             *      
             *      $('.selectable', scope).hide();
             *      
             *      $.each(allTags, function (index, value) {
             *          var guid = value.split('|')[1];
             *          $("span.metadata:contains('" + guid + "')", scope).parent().show();
             *      });
             * };
             */

            jQuery.Append("$.fn.refreshItems=function(){var scope=this;var allTags=new Array();$('#tagPickers input',scope).each(function(){var tags=$(this).val().split(';');$.merge(allTags,tags);});$('.selectable',scope).hide();$.each(allTags,function(index,value){var guid=value.split('|')[1];$(\"span.metadata:contains('\"+guid+\"')\",scope).parent().show();});};");


            /* $('.refreshBtn input').click(function () {
            *      $(this).closest('.filteredLookup').refreshItems();
            *  });
            */

            jQuery.Append("$('.refreshBtn input').click(function(){$(this).closest('.filteredLookup').refreshItems();});");

            /* 
             * $(document).ready(function () {
             *     $('.filteredLookup').each(function () {
             *         $(this).refreshItems();
             *     });
             * });
             * 
             */

            jQuery.Append("$(document).ready(function(){$('.filteredLookup').each(function(){$(this).refreshItems();});});");

            /*
            * // code used to only allow selection of one checkbox - used for single valued lookup.
            * $('.singleValue input').change(function () {
            *     if ($(this).attr("checked")) {
            *         var parent = $(this).parents('.filteredLookup');
            *         $('.singleValue input',parent).not(this).attr("checked", false);
            *     }
            *     else {
            *         // do nothing
            *     }
            * });
            * 
            */

            jQuery.Append("$('.singleValue input').change(function(){if($(this).attr(\"checked\")){var parent=$(this).parents('.filteredLookup');$('.singleValue input',parent).not(this).attr(\"checked\",false);}else{}});");


            Page.ClientScript.RegisterStartupScript(typeof(MetaFilteredFieldControl), "MetaFilteredLookupScript", jQuery.ToString(), true);
        }


        /// <summary>
        /// Initialize one TaxonomyWebTaggingControl to be preset to show the tags passed.
        /// </summary>
        /// <param name="tagPicker"></param>
        /// <param name="termStore"></param>
        /// <param name="termSetId"></param>
        /// <param name="termGroup"></param>
        /// <param name="tags"></param>
        private void InitTagPicker(TaxonomyWebTaggingControl tagPicker, TermStore termStore, TermSet termSet, IList<TaxonomyFieldValue> tags)
        {

            tagPicker.SSPList = termStore.Id.ToString();
            tagPicker.TermSetList = termSet.Id.ToString();

            // This controls whether you can add new terms to the term set
            tagPicker.AllowFillIn = false;
            // This controls whether we use an anchor term or not (by setting it to Empty we are explicitly choosing
            // not to)
            tagPicker.AnchorId = Guid.Empty;
            // Since we are bound to a specific term set and not spanning term sets the following property really does nothing
            tagPicker.ExcludeKeyword = false;
            // This controls whether unresolved terms will just be added to the termset or not
            tagPicker.IsAddTerms = false;
            // This setting allows you to browse the term set
            tagPicker.IsDisplayPickerButton = true;
            // This setting enables/disables validation highlighting
            tagPicker.IsIgnoreFormatting = false;
            tagPicker.IsIncludeDeprecated = false;
            tagPicker.IsIncludeUnavailable = false;
            // This setting modifies what is stored/returned from the control, if you want the GUIDS of parent terms
            // then set this to true
            tagPicker.IsIncludePathData = false;
            // This setting will include term set name resolution as well if set
            tagPicker.IsIncludeTermSetName = false;
            // This allows or disallows multiple values to be selected
            tagPicker.IsMulti = true;
            // We are searching on a specific termset so set these to false
            tagPicker.IsSpanTermSets = false;
            tagPicker.IsSpanTermStores = false;
            tagPicker.IsUseCommaAsDelimiter = false;
            // This is the LCID of the language to resolve terms against
            tagPicker.Language = termStore.DefaultLanguage;

            // if the termset has a description, use this instead of the default tool tip
            if (!string.IsNullOrEmpty(termSet.Description))
                tagPicker.ToolTip = termSet.Description;

            // This has a default value, however, if you want to customize what web service it uses you can
            // change the URL.
            tagPicker.WebServiceUrl = new Uri(SPContext.Current.Web.Url + "/_vti_bin/TaxonomyInternalService.json");

            tagPicker.SetCannotBeBlankErrorMessage("This control should not be blank");

            // If you want to modify the width/height of the control you will need to use the WidthCss/HeightCss 
            // properties and set them to a css class that defines the width/height properties.  The code would
            // be:
            // tagPicker.WidthCss = "my-css-class";
            // tagPicker.HeightCss = "my-css-class";

            tagPicker.Text = string.Join(TaxonomyField.TaxonomyMultipleTermDelimiter.ToString(), tags.Select(t => t.ToString()).ToArray());

        }

        /// <summary>
        /// Helper method that returns all the tags used to tag the list items passed as a parameter. The return value is grouped by term set guid, and any duplicate tags are removed.
        /// </summary>
        /// <param name="items"></param>
        /// <param name="noTags">the no tags flag is used when just the structure and not the actual tags are needed</param>
        /// <returns></returns>
        private IDictionary<Guid, IList<TaxonomyFieldValue>> GetAllActiveMetaTags(IList<SPListItem> items, bool noTags)
        {
            IDictionary<Guid, IList<TaxonomyFieldValue>> retVal = new Dictionary<Guid, IList<TaxonomyFieldValue>>();

            if (items.Count > 0)
            {
                // use the first item to get the GUIDs of the fields that are metadata fields.  Note that this code assumes all the list items are of the same content type.
                IDictionary<Guid, TaxonomyField> taxFields = new Dictionary<Guid, TaxonomyField>();
                foreach (SPField field in items[0].Fields)
                {

                    if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
                    {
                        TaxonomyField taxField = (TaxonomyField)field;
                        taxFields.Add(field.Id, taxField);
                    }
                }


                TaxonomyFieldValueComparer cmp = new TaxonomyFieldValueComparer();

                //run through the list of taxonomy fields
                foreach (KeyValuePair<Guid, TaxonomyField> taxField in taxFields)
                {
                    IList<TaxonomyFieldValue> temp = null;

                    // the no tags flag is used when just the structure and not the actual tags are needed                   
                    if (!noTags)
                    {
                        //in case of a multi valued field, use the SelectMany method, otherwise use the Select method. Also make sure the data is not null or an empty collection
                        if (taxField.Value.TypeAsString == "TaxonomyFieldTypeMulti")
                            temp = items.Cast<SPListItem>().Where(i => null != i[taxField.Key] && ((TaxonomyFieldValueCollection)i[taxField.Key]).Count > 0).SelectMany(i => i[taxField.Key] as TaxonomyFieldValueCollection).Distinct(cmp).ToList();
                        else if (taxField.Value.TypeAsString == "TaxonomyFieldType")
                            temp = items.Cast<SPListItem>().Where(i => null != i[taxField.Key]).Select(i => i[taxField.Key] as TaxonomyFieldValue).Distinct(cmp).ToList();
                    }

                    // there could already be a field in this list item that is poitned to the same term set, so need to check this.

                    if (retVal.ContainsKey(taxField.Value.TermSetId))
                    {
                        retVal[taxField.Value.TermSetId] = retVal[taxField.Value.TermSetId].Union(temp).Distinct(cmp).ToList();
                    }
                    else
                    {
                        retVal.Add(taxField.Value.TermSetId, temp);
                    }

                }
            }

            return retVal;
        }

        /// <summary>
        /// Method that returns all the metadata tags that a list item is tagged with, as a string. The format of the string is a comma delimited list of GUIDs
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private string GetMetadata(SPListItem data)
        {
            StringBuilder retVal = new StringBuilder();
            foreach (SPField field in data.Fields)
            {
                //Unfortunately we cannot check for the field.Type becase this is marked as 'invalid' for metadata fields. The TypeAsString property can be used.

                // check for a multi taxonomy field
                if (field.TypeAsString == "TaxonomyFieldTypeMulti")
                {
                    TaxonomyFieldValueCollection taxCol = (TaxonomyFieldValueCollection)data[field.Id];
                    if (null != taxCol && taxCol.Count > 0)
                        foreach (TaxonomyFieldValue tax in taxCol)
                        {
                            retVal.AppendFormat("{0},", tax.TermGuid);
                        }
                }
                // check for a single taxonomy field
                else if (field.TypeAsString == "TaxonomyFieldType")
                {
                    TaxonomyFieldValue tax = (TaxonomyFieldValue)data[field.Id];
                    if (null != tax)
                        retVal.AppendFormat("{0},", tax.TermGuid);
                }
            }
            return retVal.ToString();
        }

        /// <summary>
        /// Method that checks the checkbox corresponding to the list item referred to in the LookupValues and returns the actual list items
        /// </summary>
        /// <param name="list"></param>
        /// <param name="activeItems"></param>
        /// <param name="lv"></param>
        private IList<SPListItem> ActivateItems(SPList list, SPFieldLookupValueCollection lookupCollection)
        {
            IList<SPListItem> retVal = new List<SPListItem>();

            foreach (SPFieldLookupValue lookupValue in lookupCollection)
            {
                retVal.Add(ActivateItem(list, lookupValue));
            }
            return retVal;
        }

        /// <summary>
        /// Method that checks the checkbox corresponding to the list item referred to in the LookupValue and returns the actual list item
        /// </summary>
        /// <param name="list"></param>
        /// <param name="activeItems"></param>
        /// <param name="lv"></param>
        private SPListItem ActivateItem(SPList list, SPFieldLookupValue lookupValue)
        {
            foreach (RepeaterItem rpi in rpSelected.Items)
            {
                Literal litID = (Literal)rpi.FindControl("litID");
                if (int.Parse(litID.Text) == lookupValue.LookupId)
                {
                    CheckBox cbItem = (CheckBox)rpi.FindControl("cbItem");
                    cbItem.Checked = true;
                }
            }
            return list.GetItemById(lookupValue.LookupId);
        }


        private void WriteToLog(string error, Exception exception, TraceSeverity tSeverity, EventSeverity eSeverity)
        {
            try
            {
                error = string.Format("XComplica Metadata Filtered Field: {0}", error);
                SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
                SPDiagnosticsCategory diagCat = new SPDiagnosticsCategory("XComplica", tSeverity, eSeverity); // create a category
                //Write to the diagnostics log in SharePoint

                diagSvc.WriteTrace(10013, // custom trace id
                    diagCat, // create a category
                    tSeverity, // set the logging level of this record
                    error, // custom message
                    new object[] { error, exception } // parameters to message
                );
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("Error writing to SharePoint Diagnostic Log: {1}", ex));
            }
        }
    }
}
